package com.example.demo.common.mybatis.config;


import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.FileWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;

public class GenerateSqlmapConfigTools {

	// private SqlmapGenerator generator;
	private String driveClassName;
	private String jdbcUrl;
	private String username;
	private String password;
	private String databaseName;
	private String modelName;
	private String mapperFilePath;
	private boolean isMysql;
	private List<SqlMapConfig> tables = new ArrayList<>();
	// 数据库类型与 java类型对应
	private static Map<String, String> typeMap;

	private static Map<String, String> oracleTypeMap;

	static {
		typeMap = new HashMap<>();
		typeMap.put("BIGINT", "Long");
		// jdbc type 应该定为 DOUBLE
		typeMap.put("DECIMAL", "Double");
		typeMap.put("VARCHAR", "String");

		// jdbctype 应该定为TIMESTAMP
		typeMap.put("DATETIME", "Date");
		// jdbctype 应该定为CHAR
		typeMap.put("CHAR", "String");

		typeMap.put("LONGBLOB", "byte []");

		// jdbctype 应该定为INTEGER
		typeMap.put("INT", "Integer");

		typeMap.put("LONGTEXT", "String");
		typeMap.put("TEXT", "String");

		oracleTypeMap = new HashMap<>();
		oracleTypeMap.put("LONG", "String");
		oracleTypeMap.put("XMLTYPE", "String");
		oracleTypeMap.put("NVARCHAR2", "String");
		oracleTypeMap.put("TIMESTAMP(6)", "Date");
		// oracleTypeMap.put("NUMBER", value);
		oracleTypeMap.put("CLOB", "String");
		oracleTypeMap.put("CHAR", "String");
		// oracleTypeMap.put("UROWID", value);
		oracleTypeMap.put("DATE", "Date");
		// oracleTypeMap.put("UNDEFINED", value);
		oracleTypeMap.put("VARCHAR2", "String");
		oracleTypeMap.put("BLOB", "byte []");

	}

	/**
	 * mysql 构造函数
	 * 
	 * @param driveClassName
	 *            数据库驱动类
	 * @param jdbcUrl
	 *            jdbc路径
	 * @param username
	 *            连接数据库用户名
	 * @param password
	 *            连接数据库密码
	 * 
	 * @param databaseName
	 *            数据库名称
	 * @param mapperFilePath
	 *            mapper文件生成的位置（文件夹需自己创建）
	 */
	public GenerateSqlmapConfigTools(String driveClassName, String jdbcUrl, String username, String password,
			String databaseName, String mapperFilePath) {
		if (StringUtils.isEmpty(driveClassName)) {
			throw new RuntimeException("The drive class name  must be not null.");
		}
		if (StringUtils.isEmpty(jdbcUrl)) {
			throw new RuntimeException("The jdbc url  must be not null.");
		}
		if (StringUtils.isEmpty(username)) {
			throw new RuntimeException("The username  must be not null.");
		}
		if (StringUtils.isEmpty(password)) {
			throw new RuntimeException("The password  must be not null.");
		}
		if (StringUtils.isEmpty(databaseName)) {
			throw new RuntimeException("The databaseName  must be not null.");
		}

		this.driveClassName = driveClassName;
		this.jdbcUrl = jdbcUrl;
		this.username = username;
		this.password = password;
		this.databaseName = databaseName;
		this.mapperFilePath = mapperFilePath;
	}

	/**
	 * oracle 构造函数
	 * 
	 * @param driveClassName
	 *            数据库驱动类
	 * @param jdbcUrl
	 *            jdbc路径
	 * @param username
	 *            连接数据库用户名
	 * @param password
	 *            连接数据库密码
	 */
	public GenerateSqlmapConfigTools(String driveClassName, String jdbcUrl, String username, String password,
			String mapperFilePath) {
		if (StringUtils.isEmpty(driveClassName)) {
			throw new RuntimeException("The drive class name  must be not null.");
		}
		if (StringUtils.isEmpty(jdbcUrl)) {
			throw new RuntimeException("The jdbc url  must be not null.");
		}
		if (StringUtils.isEmpty(username)) {
			throw new RuntimeException("The username  must be not null.");
		}
		if (StringUtils.isEmpty(password)) {
			throw new RuntimeException("The password  must be not null.");
		}
		this.driveClassName = driveClassName;
		this.jdbcUrl = jdbcUrl;
		this.username = username;
		this.password = password;
		this.mapperFilePath = mapperFilePath;
	}

	/**
	 * 可添加多张表
	 * 
	 * @param config
	 *            某张表的配置
	 */
	public void addTable(SqlMapConfig config) {
		tables.add(config);
	}

	/**
	 * 
	 * 
	 * 
	 * @param configFilePosition
	 *            配置文件输出路径，供修改生成sqlxml的规则时使用（一般不修改）
	 * 
	 * @return
	 */
	public String generateXmlFile(String configFilePosition) {
		String[] names = driveClassName.split("\\.");
		List<String> l = Arrays.asList(names);
		DatabaseDao dao;
		if (l.contains("oracle")) {
			dao = new OracleDao();
			isMysql = false;
		} else {
			dao = new MysqlDao();
			isMysql = true;
		}

		Connection conn = getConnection();

		StringBuilder sb = new StringBuilder();
		sb.append(buildXmlSchema());
		sb.append("<generatorConfiguration>\n\n");

		if (tables.size() > 0) {
			try {
				for (SqlMapConfig config : tables) {
					List<Property> properties = dao.queryProperties(conn, config.getTableName(), databaseName);
					config.setProperties(properties);

					sb.append(buildTableConfig(config));
				}
			} catch (SQLException e) {
				e.printStackTrace();
			} finally {
				DbUtils.closeQuietly(conn);
			}

		}
		sb.append("</generatorConfiguration>");

		File xmlFile = new File(configFilePosition);
		output2File(sb.toString(), xmlFile);
		return configFilePosition;
	}

	// 数据库连接

	private Connection getConnection() {
		try {
			Class.forName(driveClassName);
			Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
			return connection;
		} catch (SQLException e) {
			// logger.error("Cann't open connection.", e);
			throw new RuntimeException("Cann't open connection");
		} catch (ClassNotFoundException e) {
			// logger.error("jdbc drive class not found.", e);
			throw new RuntimeException("jdbc drive class not found");
		}
	}

	private String buildTableConfig(SqlMapConfig config) {
		StringBuilder sb = new StringBuilder();
		sb.append("\t<context id=\"" + config.getTableName()
				+ "\" targetRuntime=\"MyBatis3\" defaultModelType=\"flat\" >\n");
		sb.append("\t\t<property name=\"autoDelimitKeywords\" value=\"true\"/>\n");

		if (isMysql) {
			sb.append("\t\t<property name=\"beginningDelimiter\" value=\"`\"/>\n");
			sb.append("\t\t<property name=\"endingDelimiter\" value=\"`\"/>\n");

		}
		// else {
		// sb.append("\t\t<property name=\"beginningDelimiter\" value=\"\"\"/>\n");
		// sb.append("\t\t<property name=\"endingDelimiter\" value=\"\"\"/>\n");
		// }
		sb.append("\t\t<plugin type=\"org.mybatis.generator.plugins.SerializablePlugin\"></plugin>\n");
		sb.append("\t\t<plugin type=\"org.mybatis.generator.plugins.ToStringPlugin\" />\n\n");

		sb.append("\t\t<commentGenerator>\n");
		sb.append("\t\t\t<property name=\"suppressAllComments\" value=\"true\"/>\n");
		sb.append("\t\t\t<property name=\"suppressDate\" value=\"true\"/>\n");
		sb.append("\t\t</commentGenerator>\n\n");

		sb.append("\t\t<jdbcConnection driverClass=\"" + driveClassName + "\"\n");
		sb.append("\t\t\tconnectionURL=\"" + jdbcUrl + "\" \n");
		sb.append("\t\t\tuserId=\"" + username + "\"\n");
		sb.append("\t\t\tpassword=\"" + password + "\">\n");
		sb.append("\t\t	</jdbcConnection>\n");
		sb.append("\n");

		sb.append("\t\t<javaTypeResolver>\n");
		sb.append("\t\t\t<property name=\"forceBigDecimals\" value=\"false\" />\n");
		sb.append("\t\t</javaTypeResolver>\n\n");

		// 实体类所在包配置
		sb.append("\t\t<javaModelGenerator targetPackage=\"" + config.getModelTargetPackage() + "\"\n");
		sb.append("\t\t\ttargetProject=\"" + mapperFilePath + "\">\n");
		sb.append("\t\t\t<property name=\"enableSubPackages\" value=\"false\" />\n");
		sb.append("\t\t</javaModelGenerator>\n\n");

		// sqlxml文件配置
		sb.append("\t\t<sqlMapGenerator targetPackage=\"" + config.getInterfaceTargetPackage() + "\"\n");
		sb.append("\t\t\ttargetProject=\"" + mapperFilePath + "\">\n");
		sb.append("\t\t\t<property name=\"enableSubPackages\" value=\"false\" />\n");
		sb.append("\t\t</sqlMapGenerator>\n\n");

		// 接口文件配置
		sb.append("\t\t<javaClientGenerator type=\"XMLMAPPER\"\n");
		sb.append("\t\t\ttargetPackage=\"" + config.getInterfaceTargetPackage() + "\"\n");
		sb.append("\t\t\ttargetProject=\"" + mapperFilePath + "\">\n");
		sb.append("\t\t\t<property name=\"enableSubPackages\" value=\"false\" />\n");
		sb.append("\t\t</javaClientGenerator>\n\n");

		// 表字段等具体配置
		sb.append("\t\t<table schema=\"\" tableName=\"" + config.getTableName() + "\" ");
		if (modelName != null) {
			sb.append("domainObjectName=\"" + modelName + "\"\n");
		}
		sb.append("\t\t\tenableCountByExample=\"false\" enableUpdateByExample=\"false\" enableDeleteByExample=\"false\"\n");
		sb.append("\t\t\tenableSelectByExample=\"false\" selectByExampleQueryId=\"false\">\n\n");
		List<Property> tableProperties = config.getProperties();
		for (Property property : tableProperties) {
			if (isMysql) {
				if ("PRI".equalsIgnoreCase(property.getColumnKey())
						&& "auto_increment".equalsIgnoreCase(property.getExtra())) {
					sb.append("\t\t\t<generatedKey column=\"" + property.getColumnName()
							+ "\" type=\"post\" identity=\"true\" sqlStatement=\"MySql\"/>\n");
				}

				if ("decimal".equalsIgnoreCase(property.getDataType())) {
					if (property.getNumericPrecision() != null && property.getNumericScale() != null
							&& 30 == property.getNumericPrecision() && 8 == property.getNumericScale()) {
		 				sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
								+ "BigDecimal" + "\" jdbcType=\"" + "");
						sb.append("DECIMAL");
					} else {
						sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
								+ "Double" + "\" jdbcType=\"" + "");
						sb.append("DOUBLE");
					}

				} else {
					sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
							+ typeMap.get(property.getDataType().toUpperCase()) + "\" jdbcType=\"" + "");
					switch (property.getDataType()) {
					case "datetime":
						sb.append("TIMESTAMP");
						break;
					case "text":
						sb.append("VARCHAR");
						break;
					case "longtext":
						sb.append("VARCHAR");
						break;
					case "longblob":
						sb.append("BLOB");
						break;
					case "char":
						sb.append("CHAR");
						break;
					case "int":
						sb.append("INTEGER");
						break;
					default:
						sb.append(property.getDataType().toUpperCase());
						break;
					}
				}
			} else {
				// oracle
				if ("P".equalsIgnoreCase(property.getConstraintType())) {
					sb.append("\t\t\t<generatedKey column=\"" + property.getColumnName()
							+ "\" type=\"pre\" identity=\"false\" sqlStatement=\"select SEQ_" + config.getTableName()
							+ ".nextval from dual\"/>\n");
				}
				if ("number".equalsIgnoreCase(property.getDataType())) {
					if (property.getNumericPrecision() != null && property.getNumericScale() != null
							&& 30 == property.getNumericPrecision() && 8 == property.getNumericScale()) {
						sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
								+ "BigDecimal" + "\" jdbcType=\"" + "");
						sb.append("DECIMAL");
					} else if (property.getNumericPrecision() != null && property.getNumericScale() != null) {
						sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
								+ "Double" + "\" jdbcType=\"" + "");
						sb.append("DOUBLE");
					} else {
						sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
								+ "Long" + "\" jdbcType=\"" + "");
						sb.append("BIGINT");
					}

				} else {
					sb.append("\t\t\t<columnOverride  column=\"" + property.getColumnName() + "\" javaType=\""
							+ oracleTypeMap.get(property.getDataType().toUpperCase()) + "\" jdbcType=\"" + "");
					switch (property.getDataType()) {
					case "LONG":
						sb.append("VARCHAR");
						break;
					case "XMLTYPE":
						sb.append("VARCHAR");
						break;
					case "NVARCHAR2":
						sb.append("VARCHAR");
						break;
					case "TIMESTAMP(6)":
						sb.append("TIMESTAMP");
						break;
					case "CLOB":
						sb.append("VARCHAR");
						break;
					case "CHAR":
						sb.append("CHAR");
						break;
					case "DATE":
						sb.append("TIMESTAMP");
						break;
					case "VARCHAR2":
						sb.append("VARCHAR");
						break;
					case "BLOB":
						sb.append("BLOB");
						break;
					default:
						sb.append(property.getDataType().toUpperCase());
						break;
					}
				}

			}

			sb.append("\" />\n");
		}
		sb.append("\t\t</table>\n\t</context>\n\n");

		return sb.toString();
	}

	public void output2File(String content, File file) {
		// FileWriterWithEncoding writer = new FileWriterWithEncoding(file,
		// "utf-8")

		try (FileWriter writer = new FileWriter(file)) {
			writer.write(content);
		} catch (Exception e) {
			// logger.error(e.toString());
			throw new RuntimeException(e);
		}
	}

	private String buildXmlSchema() {
		StringBuilder sb = new StringBuilder();
		sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		sb.append("<!DOCTYPE generatorConfiguration\n");
		sb.append("\tPUBLIC \"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\"\n");
		sb.append("\t\"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\">\n");

		return sb.toString();
	}

}
